#include "../../../include/common/json/reader.h"
#include "../../../include/common/json/value.h"
#include <utility>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <iostream>
#include <stdexcept>

#if _MSC_VER >= 1400            // VC++ 8.0
#pragma warning(disable : 4996) // disable warning about strdup being deprecated.
#endif

namespace Json
{

// Implementation of class Features
// ////////////////////////////////

Features::Features()
    : allowComments_(true), strictRoot_(false)
{
}

Features
Features::all()
{
  return Features();
}

Features
Features::strictMode()
{
  Features features;
  features.allowComments_ = false;
  features.strictRoot_ = true;
  return features;
}

// Implementation of class Reader
// ////////////////////////////////

static inline bool
in(Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4)
{
  return c == c1 || c == c2 || c == c3 || c == c4;
}

static inline bool
in(Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5)
{
  return c == c1 || c == c2 || c == c3 || c == c4 || c == c5;
}

static bool
containsNewLine(Reader::Location begin,
                Reader::Location end)
{
  for (; begin < end; ++begin)
    if (*begin == '\n' || *begin == '\r')
      return true;
  return false;
}

static std::string codePointToUTF8(unsigned int cp)
{
  std::string result;

  // based on description from http://en.wikipedia.org/wiki/UTF-8

  if (cp <= 0x7f)
  {
    result.resize(1);
    result[0] = static_cast<char>(cp);
  }
  else if (cp <= 0x7FF)
  {
    result.resize(2);
    result[1] = static_cast<char>(0x80 | (0x3f & cp));
    result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6)));
  }
  else if (cp <= 0xFFFF)
  {
    result.resize(3);
    result[2] = static_cast<char>(0x80 | (0x3f & cp));
    result[1] = 0x80 | static_cast<char>((0x3f & (cp >> 6)));
    result[0] = 0xE0 | static_cast<char>((0xf & (cp >> 12)));
  }
  else if (cp <= 0x10FFFF)
  {
    result.resize(4);
    result[3] = static_cast<char>(0x80 | (0x3f & cp));
    result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
    result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
    result[0] = static_cast<char>(0xF0 | (0x7 & (cp >> 18)));
  }

  return result;
}

// Class Reader
// //////////////////////////////////////////////////////////////////

Reader::Reader()
    : features_(Features::all())
{
}

Reader::Reader(const Features &features)
    : features_(features)
{
}

bool Reader::parse(const std::string &document,
                   Value &root,
                   bool collectComments)
{
  document_ = document;
  const char *begin = document_.c_str();
  const char *end = begin + document_.length();
  return parse(begin, end, root, collectComments);
}

bool Reader::parse(std::istream &sin,
                   Value &root,
                   bool collectComments)
{
  //std::istream_iterator<char> begin(sin);
  //std::istream_iterator<char> end;
  // Those would allow streamed input from a file, if parse() were a
  // template function.

  // Since std::string is reference-counted, this at least does not
  // create an extra copy.
  std::string doc;
  std::getline(sin, doc, (char)EOF);
  return parse(doc, root, collectComments);
}

bool Reader::parse(const char *beginDoc, const char *endDoc,
                   Value &root,
                   bool collectComments)
{
  if (!features_.allowComments_)
  {
    collectComments = false;
  }

  begin_ = beginDoc;
  end_ = endDoc;
  collectComments_ = collectComments;
  current_ = begin_;
  lastValueEnd_ = 0;
  lastValue_ = 0;
  commentsBefore_ = "";
  errors_.clear();
  while (!nodes_.empty())
    nodes_.pop();
  nodes_.push(&root);

  bool successful = readValue();
  Token token;
  skipCommentTokens(token);
  if (collectComments_ && !commentsBefore_.empty())
    root.setComment(commentsBefore_, commentAfter);
  if (features_.strictRoot_)
  {
    if (!root.isArray() && !root.isObject())
    {
      // Set error location to start of doc, ideally should be first token found in doc
      token.type_ = tokenError;
      token.start_ = beginDoc;
      token.end_ = endDoc;
      addError("A valid JSON document must be either an array or an object value.",
               token);
      return false;
    }
  }
  return successful;
}

bool Reader::readValue()
{
  Token token;
  skipCommentTokens(token);
  bool successful = true;

  if (collectComments_ && !commentsBefore_.empty())
  {
    currentValue().setComment(commentsBefore_, commentBefore);
    commentsBefore_ = "";
  }

  switch (token.type_)
  {
  case tokenObjectBegin:
    successful = readObject(token);
    break;
  case tokenArrayBegin:
    successful = readArray(token);
    break;
  case tokenNumber:
    successful = decodeNumber(token);
    break;
  case tokenString:
    successful = decodeString(token);
    break;
  case tokenTrue:
    currentValue() = true;
    break;
  case tokenFalse:
    currentValue() = false;
    break;
  case tokenNull:
    currentValue() = Value();
    break;
  default:
    return addError("Syntax error: value, object or array expected.", token);
  }

  if (collectComments_)
  {
    lastValueEnd_ = current_;
    lastValue_ = &currentValue();
  }

  return successful;
}

void Reader::skipCommentTokens(Token &token)
{
  if (features_.allowComments_)
  {
    do
    {
      readToken(token);
    } while (token.type_ == tokenComment);
  }
  else
  {
    readToken(token);
  }
}

bool Reader::expectToken(TokenType type, Token &token, const char *message)
{
  readToken(token);
  if (token.type_ != type)
    return addError(message, token);
  return true;
}

bool Reader::readToken(Token &token)
{
  skipSpaces();
  token.start_ = current_;
  Char c = getNextChar();
  bool ok = true;
  switch (c)
  {
  case '{':
    token.type_ = tokenObjectBegin;
    break;
  case '}':
    token.type_ = tokenObjectEnd;
    break;
  case '[':
    token.type_ = tokenArrayBegin;
    break;
  case ']':
    token.type_ = tokenArrayEnd;
    break;
  case '"':
    token.type_ = tokenString;
    ok = readString();
    break;
  case '/':
    token.type_ = tokenComment;
    ok = readComment();
    break;
  case '0':
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
  case '-':
    token.type_ = tokenNumber;
    readNumber();
    break;
  case 't':
    token.type_ = tokenTrue;
    ok = match("rue", 3);
    break;
  case 'f':
    token.type_ = tokenFalse;
    ok = match("alse", 4);
    break;
  case 'n':
    token.type_ = tokenNull;
    ok = match("ull", 3);
    break;
  case ',':
    token.type_ = tokenArraySeparator;
    break;
  case ':':
    token.type_ = tokenMemberSeparator;
    break;
  case 0:
    token.type_ = tokenEndOfStream;
    break;
  default:
    ok = false;
    break;
  }
  if (!ok)
    token.type_ = tokenError;
  token.end_ = current_;
  return true;
}

void Reader::skipSpaces()
{
  while (current_ != end_)
  {
    Char c = *current_;
    if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
      ++current_;
    else
      break;
  }
}

bool Reader::match(Location pattern,
                   int patternLength)
{
  if (end_ - current_ < patternLength)
    return false;
  int index = patternLength;
  while (index--)
    if (current_[index] != pattern[index])
      return false;
  current_ += patternLength;
  return true;
}

bool Reader::readComment()
{
  Location commentBegin = current_ - 1;
  Char c = getNextChar();
  bool successful = false;
  if (c == '*')
    successful = readCStyleComment();
  else if (c == '/')
    successful = readCppStyleComment();
  if (!successful)
    return false;

  if (collectComments_)
  {
    CommentPlacement placement = commentBefore;
    if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin))
    {
      if (c != '*' || !containsNewLine(commentBegin, current_))
        placement = commentAfterOnSameLine;
    }

    addComment(commentBegin, current_, placement);
  }
  return true;
}

void Reader::addComment(Location begin,
                        Location end,
                        CommentPlacement placement)
{
  assert(collectComments_);
  if (placement == commentAfterOnSameLine)
  {
    assert(lastValue_ != 0);
    lastValue_->setComment(std::string(begin, end), placement);
  }
  else
  {
    if (!commentsBefore_.empty())
      commentsBefore_ += "\n";
    commentsBefore_ += std::string(begin, end);
  }
}

bool Reader::readCStyleComment()
{
  while (current_ != end_)
  {
    Char c = getNextChar();
    if (c == '*' && *current_ == '/')
      break;
  }
  return getNextChar() == '/';
}

bool Reader::readCppStyleComment()
{
  while (current_ != end_)
  {
    Char c = getNextChar();
    if (c == '\r' || c == '\n')
      break;
  }
  return true;
}

void Reader::readNumber()
{
  while (current_ != end_)
  {
    if (!(*current_ >= '0' && *current_ <= '9') &&
        !in(*current_, '.', 'e', 'E', '+', '-'))
      break;
    ++current_;
  }
}

bool Reader::readString()
{
  Char c = 0;
  while (current_ != end_)
  {
    c = getNextChar();
    if (c == '\\')
      getNextChar();
    else if (c == '"')
      break;
  }
  return c == '"';
}

bool Reader::readObject(Token &tokenStart)
{
  Token tokenName;
  std::string name;
  currentValue() = Value(objectValue);
  while (readToken(tokenName))
  {
    bool initialTokenOk = true;
    while (tokenName.type_ == tokenComment && initialTokenOk)
      initialTokenOk = readToken(tokenName);
    if (!initialTokenOk)
      break;
    if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
      return true;
    if (tokenName.type_ != tokenString)
      break;

    name = "";
    if (!decodeString(tokenName, name))
      return recoverFromError(tokenObjectEnd);

    Token colon;
    if (!readToken(colon) || colon.type_ != tokenMemberSeparator)
    {
      return addErrorAndRecover("Missing ':' after object member name",
                                colon,
                                tokenObjectEnd);
    }
    Value &value = currentValue()[name];
    nodes_.push(&value);
    bool ok = readValue();
    nodes_.pop();
    if (!ok) // error already set
      return recoverFromError(tokenObjectEnd);

    Token comma;
    if (!readToken(comma) || (comma.type_ != tokenObjectEnd &&
                              comma.type_ != tokenArraySeparator &&
                              comma.type_ != tokenComment))
    {
      return addErrorAndRecover("Missing ',' or '}' in object declaration",
                                comma,
                                tokenObjectEnd);
    }
    bool finalizeTokenOk = true;
    while (comma.type_ == tokenComment &&
           finalizeTokenOk)
      finalizeTokenOk = readToken(comma);
    if (comma.type_ == tokenObjectEnd)
      return true;
  }
  return addErrorAndRecover("Missing '}' or object member name",
                            tokenName,
                            tokenObjectEnd);
}

bool Reader::readArray(Token &tokenStart)
{
  currentValue() = Value(arrayValue);
  skipSpaces();
  if (*current_ == ']') // empty array
  {
    Token endArray;
    readToken(endArray);
    return true;
  }
  int index = 0;
  while (true)
  {
    Value &value = currentValue()[index++];
    nodes_.push(&value);
    bool ok = readValue();
    nodes_.pop();
    if (!ok) // error already set
      return recoverFromError(tokenArrayEnd);

    Token token;
    // Accept Comment after last item in the array.
    ok = readToken(token);
    while (token.type_ == tokenComment && ok)
    {
      ok = readToken(token);
    }
    bool badTokenType = (token.type_ == tokenArraySeparator &&
                         token.type_ == tokenArrayEnd);
    if (!ok || badTokenType)
    {
      return addErrorAndRecover("Missing ',' or ']' in array declaration",
                                token,
                                tokenArrayEnd);
    }
    if (token.type_ == tokenArrayEnd)
      break;
  }
  return true;
}

bool Reader::decodeNumber(Token &token)
{
  bool isDouble = false;
  for (Location inspect = token.start_; inspect != token.end_; ++inspect)
  {
    isDouble = isDouble || in(*inspect, '.', 'e', 'E', '+') || (*inspect == '-' && inspect != token.start_);
  }
  if (isDouble)
    return decodeDouble(token);
  Location current = token.start_;
  bool isNegative = *current == '-';
  if (isNegative)
    ++current;
  Value::UInt threshold = (isNegative ? Value::UInt(-Value::minInt)
                                      : Value::maxUInt) /
                          10;
  Value::UInt value = 0;
  while (current < token.end_)
  {
    Char c = *current++;
    if (c < '0' || c > '9')
      return addError("'" + std::string(token.start_, token.end_) + "' is not a number.", token);
    if (value >= threshold)
      return decodeDouble(token);
    value = value * 10 + Value::UInt(c - '0');
  }
  if (isNegative)
    currentValue() = -Value::Int(value);
  else if (value <= Value::UInt(Value::maxInt))
    currentValue() = Value::Int(value);
  else
    currentValue() = value;
  return true;
}

bool Reader::decodeDouble(Token &token)
{
  double value = 0;
  const int bufferSize = 32;
  int count;
  int length = int(token.end_ - token.start_);
  if (length <= bufferSize)
  {
    Char buffer[bufferSize];
    memcpy(buffer, token.start_, length);
    buffer[length] = 0;
    count = sscanf(buffer, "%lf", &value);
  }
  else
  {
    std::string buffer(token.start_, token.end_);
    count = sscanf(buffer.c_str(), "%lf", &value);
  }

  if (count != 1)
    return addError("'" + std::string(token.start_, token.end_) + "' is not a number.", token);
  currentValue() = value;
  return true;
}

bool Reader::decodeString(Token &token)
{
  std::string decoded;
  if (!decodeString(token, decoded))
    return false;
  currentValue() = decoded;
  return true;
}

bool Reader::decodeString(Token &token, std::string &decoded)
{
  decoded.reserve(token.end_ - token.start_ - 2);
  Location current = token.start_ + 1; // skip '"'
  Location end = token.end_ - 1;       // do not include '"'
  while (current != end)
  {
    Char c = *current++;
    if (c == '"')
      break;
    else if (c == '\\')
    {
      if (current == end)
        return addError("Empty escape sequence in string", token, current);
      Char escape = *current++;
      switch (escape)
      {
      case '"':
        decoded += '"';
        break;
      case '/':
        decoded += '/';
        break;
      case '\\':
        decoded += '\\';
        break;
      case 'b':
        decoded += '\b';
        break;
      case 'f':
        decoded += '\f';
        break;
      case 'n':
        decoded += '\n';
        break;
      case 'r':
        decoded += '\r';
        break;
      case 't':
        decoded += '\t';
        break;
      case 'u':
      {
        unsigned int unicode;
        if (!decodeUnicodeCodePoint(token, current, end, unicode))
          return false;
        decoded += codePointToUTF8(unicode);
      }
      break;
      default:
        return addError("Bad escape sequence in string", token, current);
      }
    }
    else
    {
      decoded += c;
    }
  }
  return true;
}

bool Reader::decodeUnicodeCodePoint(Token &token,
                                    Location &current,
                                    Location end,
                                    unsigned int &unicode)
{

  if (!decodeUnicodeEscapeSequence(token, current, end, unicode))
    return false;
  if (unicode >= 0xD800 && unicode <= 0xDBFF)
  {
    // surrogate pairs
    if (end - current < 6)
      return addError("additional six characters expected to parse unicode surrogate pair.", token, current);
    unsigned int surrogatePair;
    if (*(current++) == '\\' && *(current++) == 'u')
    {
      if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair))
      {
        unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
      }
      else
        return false;
    }
    else
      return addError("expecting another \\u token to begin the second half of a unicode surrogate pair", token, current);
  }
  return true;
}

bool Reader::decodeUnicodeEscapeSequence(Token &token,
                                         Location &current,
                                         Location end,
                                         unsigned int &unicode)
{
  if (end - current < 4)
    return addError("Bad unicode escape sequence in string: four digits expected.", token, current);
  unicode = 0;
  for (int index = 0; index < 4; ++index)
  {
    Char c = *current++;
    unicode *= 16;
    if (c >= '0' && c <= '9')
      unicode += c - '0';
    else if (c >= 'a' && c <= 'f')
      unicode += c - 'a' + 10;
    else if (c >= 'A' && c <= 'F')
      unicode += c - 'A' + 10;
    else
      return addError("Bad unicode escape sequence in string: hexadecimal digit expected.", token, current);
  }
  return true;
}

bool Reader::addError(const std::string &message,
                      Token &token,
                      Location extra)
{
  ErrorInfo info;
  info.token_ = token;
  info.message_ = message;
  info.extra_ = extra;
  errors_.push_back(info);
  return false;
}

bool Reader::recoverFromError(TokenType skipUntilToken)
{
  int errorCount = int(errors_.size());
  Token skip;
  while (true)
  {
    if (!readToken(skip))
      errors_.resize(errorCount); // discard errors caused by recovery
    if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream)
      break;
  }
  errors_.resize(errorCount);
  return false;
}

bool Reader::addErrorAndRecover(const std::string &message,
                                Token &token,
                                TokenType skipUntilToken)
{
  addError(message, token);
  return recoverFromError(skipUntilToken);
}

Value &
Reader::currentValue()
{
  return *(nodes_.top());
}

Reader::Char
Reader::getNextChar()
{
  if (current_ == end_)
    return 0;
  return *current_++;
}

void Reader::getLocationLineAndColumn(Location location,
                                      int &line,
                                      int &column) const
{
  Location current = begin_;
  Location lastLineStart = current;
  line = 0;
  while (current < location && current != end_)
  {
    Char c = *current++;
    if (c == '\r')
    {
      if (*current == '\n')
        ++current;
      lastLineStart = current;
      ++line;
    }
    else if (c == '\n')
    {
      lastLineStart = current;
      ++line;
    }
  }
  // column & line start at 1
  column = int(location - lastLineStart) + 1;
  ++line;
}

std::string
Reader::getLocationLineAndColumn(Location location) const
{
  int line, column;
  getLocationLineAndColumn(location, line, column);
  char buffer[18 + 16 + 16 + 1];
  sprintf(buffer, "Line %d, Column %d", line, column);
  return buffer;
}

std::string
Reader::getFormatedErrorMessages() const
{
  std::string formattedMessage;
  for (Errors::const_iterator itError = errors_.begin();
       itError != errors_.end();
       ++itError)
  {
    const ErrorInfo &error = *itError;
    formattedMessage += "* " + getLocationLineAndColumn(error.token_.start_) + "\n";
    formattedMessage += "  " + error.message_ + "\n";
    if (error.extra_)
      formattedMessage += "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n";
  }
  return formattedMessage;
}

std::istream &operator>>(std::istream &sin, Value &root)
{
  Json::Reader reader;
  bool ok = reader.parse(sin, root, true);
  //JSON_ASSERT( ok );
  if (!ok)
    throw std::runtime_error(reader.getFormatedErrorMessages());
  return sin;
}

} // namespace Json
